package com.tlgen.doc.utils;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.tlgen.doc.model.Doc;
import com.tlgen.doc.model.Param;
import org.springframework.web.bind.annotation.*;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.tlgen.doc.constant.Constant.SRC_MAIN_JAVA;
import static com.tlgen.doc.constant.Constant.TARGET_CLASSES;

public class GenerateUtils {

    /**
     * 组装接口文档信息
     *
     * @param classes
     * @return
     */
    public static List<Doc> getDocs(List<String> classes) {
        List<Doc> docs = new ArrayList<>();
        for (String completeClassName : classes) {
            try {
                Class<?> aClass = Class.forName(completeClassName);
                File file = getClassNameFile(completeClassName);
                if (Objects.nonNull(file)) {
                    String classText = FileUtils.readFile(file);
                    Method[] declaredMethods = aClass.getDeclaredMethods();
                    for (Method declaredMethod : declaredMethods) {
                        Doc doc = new Doc();
                        // 获取方法名
                        doc.setName(declaredMethod.getName());
                        // 获取方法注释
                        String subsMethodBetweenText = getSubsMethodBetweenText(declaredMethod, classText);
                        doc.setComment(getMethodCommentText(subsMethodBetweenText));
                        // 获取方法返回类型
                        Class<?> returnType = declaredMethod.getReturnType();
                        Type genericReturnType = declaredMethod.getGenericReturnType();
                        doc.setReturnType(genericReturnType.getTypeName());
                        // 设置返回数据
                        if (returnType.getTypeName().contains("Object")) {
                            doc.setReturnData("{}");
                        } else if (returnType.getTypeName().contains("List")) {
                            String typeName = genericReturnType.getTypeName();
                            // 泛型
                            String genericType = typeName.substring(typeName.indexOf("<") + 1, typeName.indexOf(">"));
                            Class<?> classType = Class.forName(genericType);
                            List<Param> paramList = getParamsByClassType(classType);
                            doc.setReturnData(JSONObject.toJSONString(paramList));
                        } else {
                            // 实际的对象类型
                            List<Param> paramList = getParamsByClassType(returnType);
                            doc.setReturnData(JSONObject.toJSONString(paramList));
                        }
                        // 获取接口请求 url
                        getDocUrl(declaredMethod, doc);
                        // 获取请求入参
                        Parameter[] parameters = declaredMethod.getParameters();
                        if (parameters.length > 0) {
                            // @RequestBody
                            getDocRequestBodyInfo(parameters, doc);
                            // @RequestParam
                            getDocRequestParamInfo(parameters, subsMethodBetweenText, doc);
                            // query param
                            getDocQueryParamInfo(parameters, subsMethodBetweenText, doc);
                            // @PathVariable
                            getDocPathVariableInfo(parameters, subsMethodBetweenText, doc);
                        }
                        docs.add(doc);
                    }
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        docs = docs.stream().distinct().collect(Collectors.toList());
        return docs;
    }

    /**
     * 获取 @RequestBody 接口信息
     *
     * @param parameters
     * @param doc
     */
    public static void getDocRequestBodyInfo(Parameter[] parameters, Doc doc) {
        RequestBody requestBody = parameters[0].getAnnotation(RequestBody.class);
        if (Objects.nonNull(requestBody)) {
            Class<?> type = parameters[0].getType();
            List<Param> paramList = getParamsByClassType(type);
            doc.setParamType(type.getSimpleName());
            doc.setParamContentType("application/json");
            doc.setParamData(JSONObject.toJSONString(paramList));
        }
    }

    /**
     * 获取 @RequestParam 接口信息
     *
     * @param parameters
     * @param subsMethodBetweenText
     * @param doc
     */
    public static void getDocRequestParamInfo(Parameter[] parameters, String subsMethodBetweenText, Doc doc) {
        boolean isRequestParam = false;
        List<Param> paramList = new ArrayList<>();
        for (Parameter parameter : parameters) {
            RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
            if (Objects.nonNull(requestParam)) {
                isRequestParam = true;
                Param param = new Param();
                String value = requestParam.value();
                param.setFieldName(value);
                // 获取参数属性的中文注释
                String paramCommentByMethodText = getParamCommentByMethodText(subsMethodBetweenText, value);
                param.setFieldComment(paramCommentByMethodText);
                // 判断参数类型, 如果是引用数据类型, 继续获取引用数据类型中的详细字段
                Class<?> type = parameter.getType();
                Type parameterizedType = parameter.getParameterizedType();
                param.setFieldType(type.getSimpleName());
                if (isReferenceType(type)) {
                    // 引用数据类型
                    if (parameterizedType.getTypeName().contains("List")) {
                        param.setIsArray(true);
                        String parameterizedTypeTypeName = parameterizedType.getTypeName();
                        String substring = parameterizedTypeTypeName.substring(parameterizedTypeTypeName.indexOf("<") + 1, parameterizedTypeTypeName.indexOf(">"));
                        List<Param> paramsByClassType = null;
                        try {
                            Class<?> tClass = Class.forName(substring);
                            paramsByClassType = getParamsByClassType(tClass);
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                        param.setParamList(paramsByClassType);
                    } else {
                        param.setIsArray(false);
                        List<Param> paramsByClassType = getParamsByClassType(type);
                        param.setParamList(paramsByClassType);
                    }
                }
                paramList.add(param);
            }
        }
        if (isRequestParam) {
            doc.setParamContentType("application/x-www-form-urlencoded");
            doc.setParamData(JSONObject.toJSONString(paramList));
        }
    }

    /**
     * 获取 query param 接口信息
     * @param parameters
     * @param subsMethodBetweenText
     * @param doc
     */
    public static void getDocQueryParamInfo(Parameter[] parameters, String subsMethodBetweenText, Doc doc) {
        List<Param> queryParamList = new ArrayList<>();
        Parameter parameter = parameters[0];
        Annotation[] annotations = parameter.getAnnotations();
        if (annotations.length == 0) {
            for (Parameter item : parameters) {
                Param param = new Param();
                param.setFieldName(item.getName());
                param.setFieldType(item.getType().getSimpleName().toLowerCase());
                String value = item.getName();
                // 获取参数属性的中文注释
                String paramCommentByMethodText = getParamCommentByMethodText(subsMethodBetweenText, value);
                param.setFieldComment(paramCommentByMethodText);
                Class<?> type = item.getType();
                Type parameterizedType = item.getParameterizedType();
                param.setFieldType(type.getSimpleName());
                if (isReferenceType(type)) {
                    // 引用数据类型
                    if (parameterizedType.getTypeName().contains("List")) {
                        param.setIsArray(true);
                        String parameterizedTypeTypeName = parameterizedType.getTypeName();
                        String substring = parameterizedTypeTypeName.substring(parameterizedTypeTypeName.indexOf("<") + 1, parameterizedTypeTypeName.indexOf(">"));
                        List<Param> paramsByClassType = null;
                        try {
                            Class<?> tClass = Class.forName(substring);
                            paramsByClassType = getParamsByClassType(tClass);
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                        param.setParamList(paramsByClassType);
                    } else {
                        param.setIsArray(false);
                        List<Param> paramsByClassType = getParamsByClassType(type);
                        param.setParamList(paramsByClassType);
                    }
                }
                queryParamList.add(param);
            }
            doc.setParamContentType("query");
            doc.setParamData(JSONObject.toJSONString(queryParamList));
        }
    }

    /**
     * 获取 @PathVariable 接口信息
     *
     * @param parameters
     * @param subsMethodBetweenText
     * @param doc
     */
    public static void getDocPathVariableInfo(Parameter[] parameters, String subsMethodBetweenText, Doc doc) {
        boolean isPathVariableParam = false;
        List<Param> pathVariableParamList = new ArrayList<>();
        for (Parameter value : parameters) {
            PathVariable pathVariable = value.getAnnotation(PathVariable.class);
            if (Objects.nonNull(pathVariable)) {
                isPathVariableParam = true;
                Param param = new Param();

                // 获取参数属性的中文注释
                String paramCommentByMethodText = getParamCommentByMethodText(subsMethodBetweenText, value.getName());
                param.setFieldComment(paramCommentByMethodText);

                Class<?> type = value.getType();
                Type parameterizedType = value.getParameterizedType();
                param.setFieldType(type.getSimpleName());
                if (isReferenceType(type)) {
                    // 引用数据类型
                    if (parameterizedType.getTypeName().contains("List")) {
                        param.setIsArray(true);
                        String parameterizedTypeTypeName = parameterizedType.getTypeName();
                        String substring = parameterizedTypeTypeName.substring(parameterizedTypeTypeName.indexOf("<") + 1, parameterizedTypeTypeName.indexOf(">"));
                        List<Param> paramsByClassType = null;
                        try {
                            Class<?> tClass = Class.forName(substring);
                            paramsByClassType = getParamsByClassType(tClass);
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                        param.setParamList(paramsByClassType);
                    } else {
                        param.setIsArray(false);
                        List<Param> paramsByClassType = getParamsByClassType(type);
                        param.setParamList(paramsByClassType);
                    }
                }
                pathVariableParamList.add(param);
            }
        }
        if (isPathVariableParam) {
            doc.setParamContentType("pathVariable");
            doc.setParamData(JSONObject.toJSONString(pathVariableParamList));
        }
    }

    /**
     * 获取 className 对应的文件
     *
     * @param completeClassName
     * @return
     */
    public static File getClassNameFile(String completeClassName) {
        List<File> aFiles = Lists.newArrayList();
        FileUtils.getAllFile(aFiles);
        String className = completeClassName.substring(completeClassName.lastIndexOf(".") + 1);
        return aFiles.stream().filter(x -> {
            String fileSimpleName = x.getName().contains(".") ? x.getName().substring(0, x.getName().indexOf(".")) : x.getName();
            return !x.getAbsolutePath().contains(TARGET_CLASSES)
                    && Objects.equals(className, fileSimpleName)
                    && x.getAbsolutePath().contains(SRC_MAIN_JAVA);
        }).findFirst().orElse(null);
    }

    /**
     * 获取指定方法到上一个方法直接的文本内容
     *
     * @param declaredMethod
     * @param classText
     * @return
     */
    public static String getSubsMethodBetweenText(Method declaredMethod, String classText) {
        String subToMethodText = classText.substring(0, classText.indexOf(declaredMethod.getName() + "(") + declaredMethod.getName().length() + 1);
        int beginIndex = subToMethodText.lastIndexOf("}\n"); // 中间的方法
        if (beginIndex == -1) { // 第一个方法
            beginIndex = subToMethodText.lastIndexOf("{");
        }
        int endIndex = subToMethodText.indexOf(declaredMethod.getName() + "(");
        return subToMethodText.substring(beginIndex, endIndex);
    }

    /**
     * 获取接口请求 url
     *
     * @param declaredMethod
     * @param doc
     */
    public static void getDocUrl(Method declaredMethod, Doc doc) {
        GetMapping getMapping = declaredMethod.getAnnotation(GetMapping.class);
        PostMapping postMapping = declaredMethod.getAnnotation(PostMapping.class);
        PutMapping putMapping = declaredMethod.getAnnotation(PutMapping.class);
        DeleteMapping deleteMapping = declaredMethod.getAnnotation(DeleteMapping.class);
        RequestMapping requestMapping = declaredMethod.getAnnotation(RequestMapping.class);
        doc.setUrl("");
        if (Objects.nonNull(getMapping)) {
            String[] value = getMapping.value();
            doc.setUrl(value[0]);
        }
        if (Objects.nonNull(postMapping)) {
            String[] value = postMapping.value();
            doc.setUrl(value[0]);
        }
        if (Objects.nonNull(putMapping)) {
            String[] value = putMapping.value();
            doc.setUrl(value[0]);
        }
        if (Objects.nonNull(deleteMapping)) {
            String[] value = deleteMapping.value();
            doc.setUrl(value[0]);
        }
        if (Objects.nonNull(requestMapping)) {
            String[] value = requestMapping.value();
            doc.setUrl(value[0]);
        }
    }

    /**
     * 根据类型获取属性
     *
     * @param type
     * @return
     */
    public static List<Param> getParamsByClassType(Class<?> type) {
        Field[] declaredFields = type.getDeclaredFields();
        List<Param> paramList = new ArrayList<>();
        for (int i = 0; i < declaredFields.length; i++) {

            Param param = new Param();

            String fieldName = declaredFields[i].getName();

            param.setFieldName(fieldName);
            param.setFieldType(declaredFields[i].getType().getSimpleName());

            Class<?> fieldType = declaredFields[i].getType();
            if (isReferenceType(fieldType)) {
                // 引用数据类型
                Type genericType = declaredFields[i].getGenericType();
                if (genericType.getTypeName().contains("List")) {
                    param.setIsArray(true);
                    String typeName = genericType.getTypeName();
                    String substring = typeName.substring(typeName.indexOf("<") + 1, typeName.indexOf(">"));
                    List<Param> paramsByClassType = null;
                    try {
                        Class<?> aClass = Class.forName(substring);
                        paramsByClassType = getParamsByClassType(aClass);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                    param.setParamList(paramsByClassType);
                } else {
                    param.setIsArray(false);
                    List<Param> paramsByClassType = getParamsByClassType(fieldType);
                    param.setParamList(paramsByClassType);
                }
            }

            // 获取注释
            File file = getClassNameFile(type.getTypeName());
            if (Objects.nonNull(file)) {
                String text = FileUtils.readFile(file);
                int indexOf = text.indexOf(fieldName + ";");
                // 找 /**/ 里边的注释内容
                String firstComment = getFirstCommentText(indexOf, i, text, declaredFields);
                param.setFieldComment(firstComment);
                // 找 // 后边的注释内容
                String secondComment = getSecondCommentText(text, indexOf, i, declaredFields);
                if (StrUtil.isBlank(param.getFieldComment())) {
                    param.setFieldComment(secondComment);
                }
                // 组装参数信息
                paramList.add(param);
            }
        }
        return paramList;
    }

    /**
     * 判断是否是引用数据类型
     *
     * @param type
     * @return
     */
    public static boolean isReferenceType(Class<?> type) {
        // 如果不是基本数据类型或基本数据类型的包装类型, 即如果是引用数据类型, 获取其数据结构
        List<String> noReferenceTypes = new ArrayList<>();
        noReferenceTypes.add("Date");
        noReferenceTypes.add("Object");
        noReferenceTypes.add("String");
        noReferenceTypes.add("Byte");
        noReferenceTypes.add("byte");
        noReferenceTypes.add("Short");
        noReferenceTypes.add("short");
        noReferenceTypes.add("Integer");
        noReferenceTypes.add("int");
        noReferenceTypes.add("Long");
        noReferenceTypes.add("long");
        noReferenceTypes.add("Float");
        noReferenceTypes.add("float");
        noReferenceTypes.add("Double");
        noReferenceTypes.add("float");
        noReferenceTypes.add("Character");
        noReferenceTypes.add("char");
        noReferenceTypes.add("Boolean");
        noReferenceTypes.add("boolean");
        return !noReferenceTypes.contains(type.getSimpleName());
    }

    /**
     * 获取类中属性 // 后边的注释
     *
     * @param text
     * @param indexOf
     * @param i
     * @param declaredFields
     * @return
     */
    public static String getSecondCommentText(String text, int indexOf, int i, Field[] declaredFields) {
        String comment = "";
        String substring = text.substring(indexOf);
        if (i < declaredFields.length - 1) {
            int indexOfNext = substring.indexOf(declaredFields[i + 1].getName() + ";");
            if (indexOfNext > 0) {
                String substringNext = substring.substring(0, indexOfNext);
                if (substringNext.indexOf("//") > 0) {
                    String tmp = substringNext.substring(substringNext.indexOf("//") + 2).replaceAll(" ", "");
                    String sub = tmp.substring(0, tmp.indexOf("\n"));
                    comment = sub;
                }
            }
        } else if (i == declaredFields.length - 1) { // 最后一个属性
            if (substring.indexOf("//") > 0) {
                String tmp = substring.substring(substring.indexOf("//") + 2).replaceAll(" ", "");
                String sub = tmp.substring(0, tmp.indexOf("\n"));
                comment = sub;
            }
        }
        return comment;
    }

    /**
     * 获取类中 /** 中的注释
     *
     * @param indexOf
     * @param i
     * @param text
     * @param declaredFields
     * @return
     */
    public static String getFirstCommentText(int indexOf, int i, String text, Field[] declaredFields) {
        String comment = "";
        if (indexOf > 0) {
            if (i > 0) {
                int indexOfBefore = text.indexOf(declaredFields[i - 1].getName() + ";");
                if (indexOfBefore > 0) {
                    String substring = text.substring(indexOfBefore, indexOf);
                    int beginIndex = substring.lastIndexOf("/*");
                    int endIndex = substring.lastIndexOf("*/");
                    if (beginIndex > 0) {
                        String lastSubstring = substring.substring(beginIndex, endIndex);
                        lastSubstring = lastSubstring.replaceAll(" ", "").replaceAll("\\n", "");
                        String[] split = lastSubstring.split("\\*");
                        String fieldComment = "";
                        for (String s : split) {
                            if (!s.isEmpty() && !s.startsWith("/")) {
                                fieldComment = s;
                                break;
                            }
                        }
                        comment = fieldComment;
                    }
                }
            }
        }
        return comment;
    }

    /**
     * 获取方法的注释
     *
     * @param text
     * @return
     */
    public static String getMethodCommentText(String text) {
        String comment = "";
        int beginIndex = text.indexOf("/*");
        int endIndex = text.lastIndexOf("*/");
        if (beginIndex > 0) {
            String lastSubstring = text.substring(beginIndex, endIndex);
            lastSubstring = lastSubstring.replaceAll(" ", "").replaceAll("\\n", "");
            String[] split = lastSubstring.split("\\*");
            String fieldComment = "";
            for (String s : split) {
                if (!s.isEmpty() && !s.startsWith("/")) {
                    fieldComment = s;
                    break;
                }
            }
            comment = fieldComment;
        }
        return comment;
    }

    /**
     * 获取方法参数的注释
     *
     * @param subsMethodBetweenText
     * @param paramName
     * @return
     */
    public static String getParamCommentByMethodText(String subsMethodBetweenText, String paramName) {
        String comment = "";
        int beginIndex = subsMethodBetweenText.indexOf("/*");
        int endIndex = subsMethodBetweenText.lastIndexOf("*/");
        if (beginIndex > 0) {
            String lastSubstring = subsMethodBetweenText.substring(beginIndex, endIndex);
            lastSubstring = lastSubstring.replaceAll(" ", "").replaceAll("\\n", "");

            int indexOf = lastSubstring.indexOf("@param" + paramName);
            if (indexOf > 0) {
                String substring = lastSubstring.substring(indexOf + ("@Param" + paramName).length());
                comment = substring.substring(0, substring.indexOf("*"));
            }
        }
        return comment;
    }

}
